package com.atguigu.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.exception.NoStockException;
import com.atguigu.common.to.OrderTo;
import com.atguigu.common.to.mq.StockDetailTo;
import com.atguigu.common.to.mq.StockLockedTo;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.feign.OrderFeignService;
import com.atguigu.gulimall.ware.feign.ProductFeignService;
import com.atguigu.gulimall.ware.service.WareOrderTaskDetailService;
import com.atguigu.gulimall.ware.service.WareOrderTaskService;
import com.atguigu.gulimall.ware.vo.OrderItemVo;
import com.atguigu.gulimall.ware.vo.OrderVo;
import com.atguigu.gulimall.ware.vo.SkuHasStockVo;
import com.atguigu.gulimall.ware.vo.WareSkuLockVo;
import com.rabbitmq.client.Channel;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;

@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {
    @Autowired
    WareSkuDao wareSkuDao;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;

    @Autowired
    WareOrderTaskService wareOrderTaskService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    OrderFeignService orderFeignService;


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        /**
         *  wareId: 123,//仓库id
            skuId: 123//商品id
         */
        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();
        String wareId = (String) params.get("wareId");
        if(!StringUtils.isEmpty(wareId)){
            wrapper.eq("ware_id",wareId);
        }
        String skuId = (String) params.get("skuId");
        if(!StringUtils.isEmpty(skuId)){
            wrapper.eq("sku_id",skuId);
        }
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);
    }

    /**
     * 将采购成功的商品入库
     * @param skuId
     * @param wareId
     * @param skuNum
     */
    @Transactional
    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        //如果还没有这个库存记录则进行新增
        List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if(wareSkuEntities == null || wareSkuEntities.size()==0){
            WareSkuEntity wareSkuEntity=new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStock(skuNum);
            //远程获取Product工程的Sku的信息,如果远程调用失败，无需回滚，所以可以在这里Catch异常，不做出来，这样就不会向上保存异常，事务也就不会回滚了
            // TODO 不回滚的做法还有其他办法
            try {
                R info = productFeignService.info(skuId);
                Map<String,Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
                if (info.getCode() == 0){
                    wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
                }
            }catch (Exception e){

            }

            //库存锁定默认为0
            wareSkuEntity.setStockLocked(0);
            wareSkuDao.insert(wareSkuEntity);
        }else {
            //修改库存量
            wareSkuDao.addStock(skuId,wareId,skuNum);
        }

    }

    /**
     * 批量查询sku是否有库存，一个sku存放在多个仓库中
     * @param skuIds
     * @return
     */
    @Override
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
        List<SkuHasStockVo> stockVos = skuIds.stream().map(skuId -> {
            SkuHasStockVo vo = new SkuHasStockVo();
            //查询提前库存skuId的总库存量，需要减去锁定的库存
            Long count = baseMapper.getSkuStock(skuId);
            vo.setHasStock(count!=null && count > 0);
            vo.setSkuId(skuId);
            return vo;
        }).collect(Collectors.toList());
        return stockVos;
    }


    /**
     * 锁住订单库存
     *      库存解锁的场景
     *          1、订单下单成功，但是未支付，导致系统自动取消订单，或者用户手动取消订单都需要解锁库存
     *          2、下订单成功，库存也锁定成功，但是之后的操作出现异常，导致订单回滚，这种情况就要自动解锁库存
     * @param lockVo
     * @return
     */
    @Transactional(rollbackFor = NoStockException.class)  //指定什么异常一定要回滚，不指定时抛出运行时异常也是会自动回滚的
    @Override
    public Boolean orderLockStock(WareSkuLockVo lockVo) {
        //保存工作单信息,如订单id，用于追溯
        WareOrderTaskEntity taskEntity=new WareOrderTaskEntity();
        taskEntity.setOrderSn(lockVo.getOrderSn());
        wareOrderTaskService.save(taskEntity);

        //1、正常业务是按照下单的收获地址，选择一个最近的仓库来锁定库存，不过我们就不做那么复杂了
        //1、找到每个商品在哪个仓库都有库存
        List<OrderItemVo> locks = lockVo.getLocks();
        //封装每个订单项有库存的仓库
        List<SkuWareHasStock> skuWareHasStocks = locks.stream().map(lock -> {
            SkuWareHasStock stock = new SkuWareHasStock();
            stock.setSkuId(lock.getSkuId());
            stock.setNum(lock.getCount());
            List<Long> wareIds = wareSkuDao.wareIdsHasSkuStock(lock.getSkuId());
            stock.setWareIds(wareIds);
            return stock;
        }).collect(Collectors.toList());
        //2、锁定库存
        for (SkuWareHasStock wareHasStock : skuWareHasStocks) {
            //标注每个订单项是否锁住
            boolean stocked=false;
            Long skuId = wareHasStock.getSkuId();
            Integer num = wareHasStock.getNum();
            List<Long> wareIds = wareHasStock.getWareIds();
            if(wareIds == null || wareIds.size() ==0){
                //没有任何仓库有该商品的库存时，订单全部失败，无需再进行下一步操作了
                throw new NoStockException(skuId);
            }
            /**
             * 库存锁定时消息发送逻辑分析：
             *      1、每锁成功一个商品，就向工作单详情表添加一条记录，且发送一条消息，如果所有商品锁定成功，就将所有商品的消息发送出去
             *      2、锁定商品时，有些商品没锁定成功，导致工作单和工作单详情回滚。但消息已经发出去了，
             *      根据这些消息在工作单和工作单详情表无法查到记录，但这样并不影响，所以消息发出去无所谓，
             *      不过为了知道那些已经成功锁定的库存锁住了多少商品数量，所以我们要将工作单详情的数据当做消息发送出去
             */
            for (Long wareId : wareIds) {
                //锁定商品的库存
                Long count=wareSkuDao.lockSkuStock(skuId,wareId,num);
                if(count == 1){
                    //1代表影响了一行，即锁住成功
                    stocked=true;
                    //2、告诉MQ库存锁定成功,每锁定一件商品的库存，就发送一次消息
                    WareOrderTaskDetailEntity taskDetailEntity=new WareOrderTaskDetailEntity(null,skuId,"",num,taskEntity.getId(),wareId,1);
                    wareOrderTaskDetailService.save(taskDetailEntity);
                    StockLockedTo stockLockedTo=new StockLockedTo();
                    stockLockedTo.setId(taskDetailEntity.getId());
                    StockDetailTo detailTo=new StockDetailTo();
                    BeanUtils.copyProperties(taskDetailEntity,detailTo);
                    stockLockedTo.setDetail(detailTo);
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",stockLockedTo);
                    break;
                }else {
                    //0
                }
            }
            if(!stocked){
                //当前订单项没锁住，其他订单项也无需锁了
                throw new NoStockException(skuId);
            }
        }
        //3、锁定成功
        return true;
    }

    /**
     * 解锁库存，如果该方法正常运行，则是解锁成功
     * @param to
     */
    @Override
    public void unlockStock(StockLockedTo to) {
        StockDetailTo detail = to.getDetail();
        Long detailId = detail.getId();
        //解锁
        //1、查询数据库关于这个订单的锁定库存信息
        //1.1有：说明库存锁定成功
        //      1.1.1如果根据订单号查询不出订单，则说明订单根本不存在，这种情况一定要解锁
        //      1.1.2如果根据订单号查询出订单信息，判断订单状态，如果订单时是被取消状态状态，需要解锁。
        //1.2没有,没有的话无需解锁库存，因为库存锁定失败，锁定的库存会进行回滚
        WareOrderTaskDetailEntity detailEntity = wareOrderTaskDetailService.getById(detailId);
        if(detailEntity != null){
            Long id = to.getId();
            //判断是否需要解锁
            WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);
            R r = orderFeignService.getOrderStatus(taskEntity.getOrderSn());
            if(r.getCode() == 0){
                OrderVo orderVo = r.getData("order",new TypeReference<OrderVo>() {
                });
                if(orderVo==null || orderVo.getStatus()==4){
                    //如果订单不存在或者订单被取消，才能进行解锁
                    if(detailEntity.getLockStatus() == 1){
                        //只有锁定状态的订单才能被解锁
                        unlockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId);
                    }
                }
            }else {
                //远程调用失败，则向上抛异常，拒绝消息的消费
                throw new RuntimeException("远程调用失败");
            }
        }else {
            //无需解锁
        }
    }

    /**
     * 库存解锁，当订单关闭后主动解锁库存
     *      解决的问题：
     *          1、由于网络原因，导致订单超时之后状态并未及时发生改变，导致库存消息优先到期，库存服务锁定了库存，
     *              之后订单状态才改为已取消，这时候库存永远都不会解锁
     * @param to
     */
    @Override
    public void unlockStock(OrderTo to) {
        String orderSn = to.getOrderSn();
        WareOrderTaskEntity taskEntity=wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
        Long taseId = taskEntity.getId();
        List<WareOrderTaskDetailEntity> detailEntities = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>()
                .eq("task_id", taseId).eq("lock_status", 1));
        if(detailEntities!=null && detailEntities.size()>0){
            for (WareOrderTaskDetailEntity detailEntity : detailEntities) {
                unlockStock(detailEntity.getSkuId(),detailEntity.getWareId(),detailEntity.getSkuNum(),detailEntity.getId());
            }
        }
    }

    /**
     * 库存解锁，补偿库存
     * @param skuId
     * @param wareId
     * @param num
     * @param taskDetailId
     */
    private void unlockStock(Long skuId,Long wareId,Integer num,Long taskDetailId){
        //1、库存解锁
        wareSkuDao.unlockStock(skuId,wareId,num);
        //2、更新工作单详情表的状态为已解锁
        WareOrderTaskDetailEntity taskDetailEntity=new WareOrderTaskDetailEntity();
        taskDetailEntity.setId(taskDetailId);
        taskDetailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(taskDetailEntity);
    }

    @Data
    class SkuWareHasStock{
        private Long skuId;
        private Integer num;
        private List<Long> wareIds;
    }


}